package com.beidu.lottery.call.service.portal.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.beidu.lottery.call.constant.ActivityTypeEnum;
import com.beidu.lottery.call.constant.CacheKey;
import com.beidu.lottery.call.errorcode.CommonCode;
import com.beidu.lottery.call.dao.ActivityRuleMapper;
import com.beidu.lottery.call.entity.ActivityRule;
import com.beidu.lottery.call.entity.PortalActivityRule;
import com.beidu.lottery.call.entity.vo.ActivityInfoStep2VO;
import com.beidu.lottery.call.exception.ServiceException;
import com.beidu.lottery.call.service.portal.ActivityRuleService;
import com.beidu.lottery.call.utils.RedisUtil;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 活动规则表 服务实现类
 * </p>
 *
 * @author 北渡
 * @since 2023-08-10
 */
@Slf4j
@Service
public class ActivityRuleServiceImpl extends ServiceImpl<ActivityRuleMapper, ActivityRule> implements ActivityRuleService {

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private RBloomFilter<String> activityExistBloomFilter;

    /**
     * 缓存键Map
     */
    private final Map<Integer, String> cacheKeyMap = new HashMap<Integer, String>() {{
        put(0, CacheKey.RULE_REG);// 报名
        put(1, CacheKey.RULE_VOTE);// 投票
        put(2, CacheKey.RULE_LOTTERY);// 抽奖
    }};

    /**
     * 根据不同活动类型适配不同缓存键
     *
     * @param type 活动类型
     * @author 北渡
     */
    private String getCacheKey(ActivityTypeEnum type) {
        String key = this.cacheKeyMap.get(type.getCode());
        return Optional.ofNullable(key).orElseThrow(() -> new ServiceException(CommonCode.ACTIVITY_NOT_EXIST.getMsg()));
    }

    @Override
    public PortalActivityRule getActivityRuleCache(Integer id, ActivityTypeEnum type) {
        String key = this.getCacheKey(type) + id;
        // 判断活动id是否在缓存中
        Object obj = redisUtil.get(key);
        if (obj != null) {
            log.info("活动id命中缓存");
            return (PortalActivityRule) obj;
        }
        // 判断活动id是否存在布隆过滤器中
        if (!activityExistBloomFilter.contains(String.valueOf(id))) {
            // 不存在直接返回
            log.info("未命中活动id布隆过滤器");
            throw new ServiceException("活动不存在");
        }
        // 查询 Redis 缓存中是否存在活动id的空值信息，如果有代表活动不存在，直接返回
        String activityIsNullCacheKey = String.format(CacheKey.ACTIVITY_IS_NULL_KEY, id);
        if (redisUtil.hasKey(activityIsNullCacheKey)) {
            log.info("命中空活动id缓存");
            throw new ServiceException("活动不存在");
        }
        // 分布式锁 + 双重判定锁穿透查询缓存和数据库
        PortalActivityRule rule = redisUtil.computeIfAbsent(key, () -> baseMapper.queryPortalActivityRule(id, type.getCode()));
        if (rule == null) {
            // 缓存空值1分钟
            log.info("数据库查询为空添加空缓存");
            redisUtil.set(activityIsNullCacheKey, "", 1L, TimeUnit.MINUTES);
            throw new ServiceException("活动不存在");
        }
        return rule;
    }

    @Override
    public ActivityInfoStep2VO queryActivityRules(Integer activityId) {
        return baseMapper.queryActivityRule(activityId);
    }

}
